iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 20
0
Software Development

Android Architecture系列 第 20

Test part 4:ViewModel

  • 分享至 

  • xImage
  •  

今天測試ViewModel會比較輕鬆,一來ViewModel本身邏輯比較簡單,只跟repository互動所以要mock的物件少,二來苦痛都在前兩天經歷了,把學會的東西拿出來用就好。

ViewModel的code比較短所以貼在這方便待會對照。

public class RepoViewModel extends ViewModel {

    private final MutableLiveData<String> query = new MutableLiveData<>();

    private final LiveData<Resource<List<Repo>>> repos;

    private RepoRepository mRepoRepository;

    @Inject
    public RepoViewModel(RepoRepository repoRepository) {
        super();
        mRepoRepository = repoRepository;
        repos = Transformations.switchMap(query, new Function<String, LiveData<Resource<List<Repo>>>>() {
            @Override
            public LiveData<Resource<List<Repo>>> apply(String userInput) {
                if (userInput == null || userInput.isEmpty()) {
                    return AbsentLiveData.create();
                } else {
                    return mRepoRepository.search(userInput);
                }
            }
        });
    }

    LiveData<Resource<List<Repo>>> getRepos() {
        return repos;
    }

    void searchRepo(String userInput) {
        query.setValue(userInput);
    }
}

Testing ViewModel

起手式一樣是加入@Rule及mock物件。

@Rule
public InstantTaskExecutorRule instantExecutor = new InstantTaskExecutorRule();

private RepoViewModel viewModel;

private RepoRepository repository;

@Before
public void init() {
    repository = mock(RepoRepository.class);
    viewModel = new RepoViewModel(repository);
}

@Test的部分就比較自由可以依需求寫,例如ViewModel實例化時constructor中的repos有沒有成功設置:

@Test
public void testNotNull() {
    assertThat(viewModel.getRepos(), notNullValue());
    verify(repository, never()).search(anyString());
}

當LiveData沒有observer時不會呼叫repository進行搜尋:

@Test
public void dontFetchWithoutObservers() {
    viewModel.searchRepo("foo");
    verify(repository, never()).search(anyString());
}

接著是今天唯一有用新東西的地方,當LiveData有observer時驗證ViewModel的搜尋關鍵字和repository的關鍵字相同:

@Test
public void fetchWhenObserved() {
    ArgumentCaptor<String> input = ArgumentCaptor.forClass(String.class);
    
    viewModel.getRepos().observeForever(mock(Observer.class));
    viewModel.searchRepo("foo");
    
    verify(repository, times(1)).search(input.capture());
    assertThat(input.getValue(), is("foo"));
}

新面孔ArgumentCaptor可以用來驗證argument value,在search的時候用capture()把value存下來,接著assertThat(input.getValue(), is("foo"))驗證存的value是不是"foo",是就表示search的argument是"foo"。

使用ArgumentCaptor來驗證argument的好處是程式闡述性比較高,capture()gerValue()會比直接寫字串"foo"更容易表明用意。

另一個新面孔times(int)表示期望的執行次數,上例中驗證search只被執行一次。

兩者可以搭配起來測試執行多次的method,因為capture()能夠執行多次,每一次的value都會存下來變成List並於之後用getAllValues()取得,加上times(int)就能同時驗證執行次數和每次的argument value。

最後一個是測試input為null時的處理,沒有用到新的東西。

@Test
public void nullSearchInput() {
    Observer<Resource<List<Repo>>> observer = mock(Observer.class);
    viewModel.searchRepo("foo");
    viewModel.searchRepo(null);
    viewModel.getRepos().observeForever(observer);
    verify(observer).onChanged(null);
}

今天內容比較少都是code在充版面,因為明天View的測試會用Espresso在Android裝置上運行,整體實作方式不同所以另外寫。

GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day20-test-viewmodel


上一篇
Test part 3:Repository
下一篇
Test part 5:View
系列文
Android Architecture30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言